package flag

import (
	"reflect"
	"testing"
)

func TestStringColonSeparatedMultimapStringString(t *testing.T) {
	var nilMap map[string][]string
	cases := []struct {
		desc   string
		m      *ColonSeparatedMultimapStringString
		expect string
	}{
		{"nil", NewColonSeparatedMultimapStringString(&nilMap), ""},
		{"empty", NewColonSeparatedMultimapStringString(&map[string][]string{}), ""},
		{"empty key", NewColonSeparatedMultimapStringString(
			&map[string][]string{
				"": {"foo"},
			}),
			":foo"},
		{"one key", NewColonSeparatedMultimapStringString(
			&map[string][]string{
				"one": {"foo"},
			}),
			"one:foo"},
		{"two keys", NewColonSeparatedMultimapStringString(
			&map[string][]string{
				"one": {"foo"},
				"two": {"bar"},
			}),
			"one:foo,two:bar"},
		{"two keys, multiple items in one key", NewColonSeparatedMultimapStringString(
			&map[string][]string{
				"one": {"foo", "baz"},
				"two": {"bar"},
			}),
			"one:foo,one:baz,two:bar"},
		{"three keys, multiple items in one key", NewColonSeparatedMultimapStringString(
			&map[string][]string{
				"a": {"hello"},
				"b": {"again", "beautiful"},
				"c": {"world"},
			}),
			"a:hello,b:again,b:beautiful,c:world"},
	}
	for _, c := range cases {
		t.Run(c.desc, func(t *testing.T) {
			str := c.m.String()
			if c.expect != str {
				t.Fatalf("expect %q but got %q", c.expect, str)
			}
		})
	}
}

func TestSetColonSeparatedMultimapStringString(t *testing.T) {
	var nilMap map[string][]string
	cases := []struct {
		desc   string
		vals   []string
		start  *ColonSeparatedMultimapStringString
		expect *ColonSeparatedMultimapStringString
		err    string
	}{
		// we initialize the map with a default key that should be cleared by Set
		{"clears defaults", []string{""},
			NewColonSeparatedMultimapStringString(&map[string][]string{"default": {}}),
			&ColonSeparatedMultimapStringString{
				initialized: true,
				Multimap:    &map[string][]string{}}, ""},
		// make sure we still allocate for "initialized" multimaps where Multimap was initially set to a nil map
		{"allocates map if currently nil", []string{""},
			&ColonSeparatedMultimapStringString{initialized: true, Multimap: &nilMap},
			&ColonSeparatedMultimapStringString{
				initialized: true,
				Multimap:    &map[string][]string{},
			}, ""},
		// for most cases, we just reuse nilMap, which should be allocated by Set, and is reset before each test case
		{"empty", []string{""},
			NewColonSeparatedMultimapStringString(&nilMap),
			&ColonSeparatedMultimapStringString{
				initialized: true,
				Multimap:    &map[string][]string{}}, ""},
		{"empty key", []string{":foo"},
			NewColonSeparatedMultimapStringString(&nilMap),
			&ColonSeparatedMultimapStringString{
				initialized: true,
				Multimap: &map[string][]string{
					"": {"foo"},
				}}, ""},
		{"one key", []string{"one:foo"},
			NewColonSeparatedMultimapStringString(&nilMap),
			&ColonSeparatedMultimapStringString{
				initialized: true,
				Multimap: &map[string][]string{
					"one": {"foo"},
				}}, ""},
		{"two keys", []string{"one:foo,two:bar"},
			NewColonSeparatedMultimapStringString(&nilMap),
			&ColonSeparatedMultimapStringString{
				initialized: true,
				Multimap: &map[string][]string{
					"one": {"foo"},
					"two": {"bar"},
				}}, ""},
		{"two keys with space", []string{"one:foo, two:bar"},
			NewColonSeparatedMultimapStringString(&nilMap),
			&ColonSeparatedMultimapStringString{
				initialized: true,
				Multimap: &map[string][]string{
					"one": {"foo"},
					"two": {"bar"},
				}}, ""},
		{"two keys, multiple items in one key", []string{"one: foo, two:bar, one:baz"},
			NewColonSeparatedMultimapStringString(&nilMap),
			&ColonSeparatedMultimapStringString{
				initialized: true,
				Multimap: &map[string][]string{
					"one": {"foo", "baz"},
					"two": {"bar"},
				}}, ""},
		{"three keys, multiple items in one key", []string{"a:hello,b:again,c:world,b:beautiful"},
			NewColonSeparatedMultimapStringString(&nilMap),
			&ColonSeparatedMultimapStringString{
				initialized: true,
				Multimap: &map[string][]string{
					"a": {"hello"},
					"b": {"again", "beautiful"},
					"c": {"world"},
				}}, ""},
		{"three keys, multiple items in one key, multiple Set invocations", []string{"a:hello,b:again", "c:world", "b:beautiful"},
			NewColonSeparatedMultimapStringString(&nilMap),
			&ColonSeparatedMultimapStringString{
				initialized: true,
				Multimap: &map[string][]string{
					"a": {"hello"},
					"b": {"again", "beautiful"},
					"c": {"world"},
				}}, ""},
		{"missing value", []string{"a"},
			NewColonSeparatedMultimapStringString(&nilMap),
			nil,
			"malformed pair, expect string:string"},
		{"no target", []string{"a:foo"},
			NewColonSeparatedMultimapStringString(nil),
			nil,
			"no target (nil pointer to map[string][]string)"},
	}

	for _, c := range cases {
		nilMap = nil
		t.Run(c.desc, func(t *testing.T) {
			var err error
			for _, val := range c.vals {
				err = c.start.Set(val)
				if err != nil {
					break
				}
			}
			if c.err != "" {
				if err == nil || err.Error() != c.err {
					t.Fatalf("expect error %s but got %v", c.err, err)
				}
				return
			} else if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			if !reflect.DeepEqual(c.expect, c.start) {
				t.Fatalf("expect %#v but got %#v", c.expect, c.start)
			}
		})
	}
}

func TestRoundTripColonSeparatedMultimapStringString(t *testing.T) {
	cases := []struct {
		desc   string
		vals   []string
		expect string
	}{
		{"empty", []string{""}, ""},
		{"empty key", []string{":foo"}, ":foo"},
		{"one key", []string{"one:foo"}, "one:foo"},
		{"two keys", []string{"one:foo,two:bar"}, "one:foo,two:bar"},
		{"two keys, multiple items in one key", []string{"one:foo, two:bar, one:baz"}, "one:foo,one:baz,two:bar"},
		{"three keys, multiple items in one key", []string{"a:hello,b:again,c:world,b:beautiful"}, "a:hello,b:again,b:beautiful,c:world"},
		{"three keys, multiple items in one key, multiple Set invocations", []string{"a:hello,b:again", "c:world", "b:beautiful"}, "a:hello,b:again,b:beautiful,c:world"},
	}

	for _, c := range cases {
		t.Run(c.desc, func(t *testing.T) {
			m := NewColonSeparatedMultimapStringString(&map[string][]string{})
			for _, val := range c.vals {
				if err := m.Set(val); err != nil {
					t.Fatalf("unexpected error: %v", err)
				}
			}
			str := m.String()
			if c.expect != str {
				t.Fatalf("expect %q but got %q", c.expect, str)
			}
		})
	}
}

func TestEmptyColonSeparatedMultimapStringString(t *testing.T) {
	var nilMap map[string][]string
	cases := []struct {
		desc   string
		val    *ColonSeparatedMultimapStringString
		expect bool
	}{
		{"nil", NewColonSeparatedMultimapStringString(&nilMap), true},
		{"empty", NewColonSeparatedMultimapStringString(&map[string][]string{}), true},
		{"populated", NewColonSeparatedMultimapStringString(&map[string][]string{"foo": {}}), false},
	}
	for _, c := range cases {
		t.Run(c.desc, func(t *testing.T) {
			result := c.val.Empty()
			if result != c.expect {
				t.Fatalf("expect %t but got %t", c.expect, result)
			}
		})
	}
}
